/*-------------------------------------------------------------------------
 *
 * pmsignal.c
 *	  主进程与其子进程间信号的例程
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/storage/ipc/pmsignal.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <signal.h>
#include <unistd.h>

#ifdef HAVE_SYS_PRCTL_H
#include <sys/prctl.h>
#endif

#include "miscadmin.h"
#include "postmaster/postmaster.h"
#include "replication/walsender.h"
#include "storage/pmsignal.h"
#include "storage/shmem.h"
#include "utils/memutils.h"


/*
 * postmaster通过发送SIGUSR1向其子进程发出信号。
 * 具体原因通过共享内存中的标志进行传达。我们为每个可能的“原因”保留一个布尔标志，
 * 这样不同的原因可以通过不同的后端同时发出信号。（然而，
 * 如果相同的原因同时多次发出信号，postmaster只会观察到一次。）
 *
 * 这些标志实际上被声明为“volatile sig_atomic_t”以确保最大
 * 可移植性。这应该确保对标志值的加载和存储是原子操作，
 * 使我们可以省去任何显式锁定。
 *
 * 除了每个原因的标志，我们还存储一组每个子进程的标志，
 * 目前仅用于检测后端是否在没有正常关闭的情况下退出。
 * 每个子进程的标志有三种可能状态：UNUSED、ASSIGNED、ACTIVE。
 * UNUSED插槽可用于分配。ASSIGNED插槽与postmaster子进程相关，
 * 但该进程尚未接触共享内存，或已成功清理自身。ACTIVE插槽表示
 * 该进程正在积极使用共享内存。这些插槽随机分配给
 * 子进程，postmaster.c负责跟踪哪个与哪个PID相关联。
 *
 * 实际上还有第四种状态，WALSENDER。这就像ACTIVE，
 * 但携带附加信息，表明子进程是WAL发送者。
 * WAL发送者也以ACTIVE状态开始，但一旦开始流式传输WAL便切换到
 * WALSENDER（而且之后它们永远不会回到ACTIVE）。
 *
 * 我们还有一个用于反向通信的共享内存字段，
 * 从postmaster到子进程：它告诉为什么postmaster广播了
 * SIGQUIT信号（如果确实这样做了）。
 */

#define PM_CHILD_UNUSED		0	/* 这些值必须适合sig_atomic_t */
#define PM_CHILD_ASSIGNED	1
#define PM_CHILD_ACTIVE		2
#define PM_CHILD_WALSENDER	3

/* "typedef struct PMSignalData PMSignalData" 在pmsignal.h中出现 */
struct PMSignalData
{
	/* 用于向postmaster发出信号的每个原因标志 */
	sig_atomic_t PMSignalFlags[NUM_PMSIGNALS];
	/* 从postmaster到子进程的全局信号标志 */
	QuitSignalReason sigquit_reason;	/* 为什么发送了SIGQUIT */
	/* 每个子进程的标志 */
	int			num_child_flags;	/* PMChildFlags[]中的条目数 */
	sig_atomic_t PMChildFlags[FLEXIBLE_ARRAY_MEMBER];
};

/* PMSignalState指针在postmaster和子进程中都是有效的 */
NON_EXEC_STATIC volatile PMSignalData *PMSignalState = NULL;

/*
 * 这些静态变量仅在postmaster中有效。我们保持一个
 * 复制的私有数组，以便即使某个失败的子进程已经覆盖了
 * 共享内存中的PMSignalData结构，我们也可以信任其状态。
 */
static int	num_child_inuse;	/* PMChildInUse[]中的条目数 */
static int	next_child_inuse;	/* 下一个要尝试分配的插槽 */
static bool *PMChildInUse;		/* 如果第i个标志插槽已分配，则为true */

/*
 * 信号处理程序，以便在postmaster死掉时通知。
 */
#ifdef USE_POSTMASTER_DEATH_SIGNAL
volatile sig_atomic_t postmaster_possibly_dead = false;

static void fc_postmaster_death_handler(int fc_signo)
{
	postmaster_possibly_dead = true;
}

/*
 * 可用信号取决于操作系统。SIGUSR1和SIGUSR2已经
 * 用于其他目的，所以选择另一个信号。
 *
 * 目前，我们假设我们总是可以找到一个信号来使用。
 * 这个假设对于所有现代化程度足够的系统来说似乎是合理的
 * 以便具备父进程死亡信号机制。
 */
#if defined(SIGINFO)
#define POSTMASTER_DEATH_SIGNAL SIGINFO
#elif defined(SIGPWR)
#define POSTMASTER_DEATH_SIGNAL SIGPWR
#else
#error "cannot find a signal to use for postmaster death"
#endif

#endif							/* USE_POSTMASTER_DEATH_SIGNAL */

/*
 * PMSignalShmemSize
 *		计算pmsignal.c共享内存所需的空间
 */
Size PMSignalShmemSize(void)
{
	Size		fc_size;

	fc_size = offsetof(PMSignalData, PMChildFlags);
	fc_size = add_size(fc_size, mul_size(MaxLivePostmasterChildren(),
								   sizeof(sig_atomic_t)));

	return fc_size;
}

/*
 * PMSignalShmemInit - 在创建共享内存期间初始化
 */
void PMSignalShmemInit(void)
{
	bool		fc_found;

	PMSignalState = (PMSignalData *)
		ShmemInitStruct("PMSignalState", PMSignalShmemSize(), &fc_found);

	if (!fc_found)
	{
		/* 初始化所有标志为零 */
		MemSet(unvolatize(PMSignalData *, PMSignalState), 0, PMSignalShmemSize());
		num_child_inuse = MaxLivePostmasterChildren();
		PMSignalState->num_child_flags = num_child_inuse;

		/*
		 * 还分配postmaster的私有PMChildInUse[]数组。我们
		 * 可能已经在之前的共享内存创建周期中完成了这一操作，
		 * 在这种情况下释放旧数组以避免内存泄漏。（这样做是为了支持
		 * MaxLivePostmasterChildren可能已更改的情况。）在独立后端中，
		 * 我们不需要这个。
		 */
		if (PostmasterContext != NULL)
		{
			if (PMChildInUse)
				pfree(PMChildInUse);
			PMChildInUse = (bool *)
				MemoryContextAllocZero(PostmasterContext,
									   num_child_inuse * sizeof(bool));
		}
		next_child_inuse = 0;
	}
}

/*
 * SendPostmasterSignal - 从子进程向postmaster发出信号
 */
void SendPostmasterSignal(PMSignalReason fc_reason)
{
	/* 如果在独立后端中调用，则不执行任何操作 */
	if (!IsUnderPostmaster)
		return;
	/* 原子地设置适当的标志 */
	PMSignalState->PMSignalFlags[fc_reason] = true;
	/* 发送信号到邮政管理员 */
	kill(PostmasterPid, SIGUSR1);
}

/*
 * CheckPostmasterSignal - 检查是否有特定原因已被
 * 发出信号，并清除信号标志。 应在接收到 SIGUSR1 后由邮政管理员调用。
 */
bool CheckPostmasterSignal(PMSignalReason fc_reason)
{
	/* 小心 --- 如果我们还没有看到它被设置，不要清除标志 */
	if (PMSignalState->PMSignalFlags[fc_reason])
	{
		PMSignalState->PMSignalFlags[fc_reason] = false;
		return true;
	}
	return false;
}

/*
 * SetQuitSignalReason - 广播系统关闭的原因。
 * 应在向子进程发送 SIGQUIT 之前由邮政管理员调用。
 *
 * 注意：在崩溃和重启的情况下，“原因”字段在重建共享内存时会被清除；
 * 邮政管理员不需要明确地执行此操作。
 */
void SetQuitSignalReason(QuitSignalReason fc_reason)
{
	PMSignalState->sigquit_reason = fc_reason;
}

/*
 * GetQuitSignalReason - 获取系统关闭的原因。
 * 当子进程接收到 SIGQUIT 时调用。
 * 如果邮政管理员实际上没有发送 SIGQUIT，将返回 PMQUIT_NOT_SENT。
 */
QuitSignalReason
GetQuitSignalReason(void)
{
	/* 这是在信号处理程序中调用的，所以要额外小心。 */
	if (!IsUnderPostmaster || PMSignalState == NULL)
		return PMQUIT_NOT_SENT;
	return PMSignalState->sigquit_reason;
}


/*
 * AssignPostmasterChildSlot - 为新的邮政管理员子进程选择一个未使用的插槽，
 * 并将其状态设置为 ASSIGNED。 返回一个插槽编号
 * (从一到 N)。
 *
 * 只有邮政管理员被允许执行此例程，因此我们不需要任何
 * 特殊锁定。
 */
int AssignPostmasterChildSlot(void)
{
	int			fc_slot = next_child_inuse;
	int			fc_n;

	/*
	 * 扫描空闲插槽。 注意，我们对 PMSignalState 的内容不信任，
	 * 但仅使用邮政管理员本地数据进行此决定。
	 * 我们跟踪最后分配的插槽，以免浪费时间重复
	 * 扫描低编号插槽。
	 */
	for (fc_n = num_child_inuse; fc_n > 0; fc_n--)
	{
		if (--fc_slot < 0)
			fc_slot = num_child_inuse - 1;
		if (!PMChildInUse[fc_slot])
		{
			PMChildInUse[fc_slot] = true;
			PMSignalState->PMChildFlags[fc_slot] = PM_CHILD_ASSIGNED;
			next_child_inuse = fc_slot;
			return fc_slot + 1;
		}
	}

	/* 插槽耗尽...这不应该发生，否则 postmaster.c 会搞砸 */
	elog(FATAL, "no free slots in PMChildFlags array");
	return 0;					/* 保持编译器安静 */
}

/*
 * ReleasePostmasterChildSlot - 在邮政管理员子进程死亡后释放插槽。
 * 必须在邮政管理员进程中调用此函数。
 *
 * 如果插槽处于 ASSIGNED 状态（预期情况），则返回 true；
 * 否则返回 false（意味着子进程未能自我清理）。
 */
bool ReleasePostmasterChildSlot(int fc_slot)
{
	bool		fc_result;

	Assert(fc_slot > 0 && fc_slot <= num_child_inuse);
	fc_slot--;

	/*
	 * 注意：插槽状态可能已经未使用，因为
	 * postmaster.c 中的逻辑可能在子进程崩溃时多次调用此函数。
	 * 所以我们不试图对状态做任何断言。
	 */
	fc_result = (PMSignalState->PMChildFlags[fc_slot] == PM_CHILD_ASSIGNED);
	PMSignalState->PMChildFlags[fc_slot] = PM_CHILD_UNUSED;
	PMChildInUse[fc_slot] = false;
	return fc_result;
}

/*
 * IsPostmasterChildWalSender - 检查给定插槽是否被
 * walsender 进程使用。 这仅由邮政管理员调用。
 */
bool IsPostmasterChildWalSender(int fc_slot)
{
	Assert(fc_slot > 0 && fc_slot <= num_child_inuse);
	fc_slot--;

	if (PMSignalState->PMChildFlags[fc_slot] == PM_CHILD_WALSENDER)
		return true;
	else
		return false;
}

/*
 * MarkPostmasterChildActive - 将邮政管理员子进程标记为即将开始
 * 积极使用共享内存。 这在子进程中调用。
 */
void MarkPostmasterChildActive(void)
{
	int			fc_slot = MyPMChildSlot;

	Assert(fc_slot > 0 && fc_slot <= PMSignalState->num_child_flags);
	fc_slot--;
	Assert(PMSignalState->PMChildFlags[fc_slot] == PM_CHILD_ASSIGNED);
	PMSignalState->PMChildFlags[fc_slot] = PM_CHILD_ACTIVE;
}

/*
 * MarkPostmasterChildWalSender - 将邮政管理员子进程标记为 WAL 发送器
 * 进程。 这在子进程中调用，通常在将子进程标记为活动之后。
 */
void MarkPostmasterChildWalSender(void)
{
	int			fc_slot = MyPMChildSlot;

	Assert(am_walsender);

	Assert(fc_slot > 0 && fc_slot <= PMSignalState->num_child_flags);
	fc_slot--;
	Assert(PMSignalState->PMChildFlags[fc_slot] == PM_CHILD_ACTIVE);
	PMSignalState->PMChildFlags[fc_slot] = PM_CHILD_WALSENDER;
}

/*
 * MarkPostmasterChildInactive - 将邮政管理员子进程标记为完成使用
 * 共享内存。 这在子进程中调用。
 */
void MarkPostmasterChildInactive(void)
{
	int			fc_slot = MyPMChildSlot;

	Assert(fc_slot > 0 && fc_slot <= PMSignalState->num_child_flags);
	fc_slot--;
	Assert(PMSignalState->PMChildFlags[fc_slot] == PM_CHILD_ACTIVE ||
		   PMSignalState->PMChildFlags[fc_slot] == PM_CHILD_WALSENDER);
	PMSignalState->PMChildFlags[fc_slot] = PM_CHILD_ASSIGNED;
}


/*
 * PostmasterIsAliveInternal - 检查邮政管理员进程是否仍然存活
 *
 * 这是 PostmasterIsAlive() 的慢路径，调用者已经
 * 检查了 'postmaster_possibly_dead'。 （在不支持父进程死亡信号的平台上，
 * PostmasterIsAlive() 仅是这个的别名。）
 */
bool PostmasterIsAliveInternal(void)
{
#ifdef USE_POSTMASTER_DEATH_SIGNAL
	/*
	 * 在检查之前重置标志，以便在邮政管理员
	 * 在检查之后立即死亡时不会错过信号。 如果邮政管理员确实死亡，
	 * 我们将在返回给调用者之前重新准备它。
	 */
	postmaster_possibly_dead = false;
#endif

#ifndef WIN32
	{
		char		fc_c;
		ssize_t		fc_rc;

		fc_rc = read(postmaster_alive_fds[POSTMASTER_FD_WATCH], &fc_c, 1);

		/*
		 * 在通常情况下，邮政管理员仍然存活，并且管道中没有
		 * 数据。
		 */
		if (fc_rc < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
			return true;
		else
		{
			/*
			 * 邮政管理员已死，或者在 read() 调用时出现问题。
			 */

#ifdef USE_POSTMASTER_DEATH_SIGNAL
			postmaster_possibly_dead = true;
#endif

			if (fc_rc < 0)
				elog(FATAL, "read on postmaster death monitoring pipe failed: %m");
			else if (fc_rc > 0)
				elog(FATAL, "unexpected data in postmaster death monitoring pipe");

			return false;
		}
	}

#else							/* WIN32 */
	if (WaitForSingleObject(PostmasterHandle, 0) == WAIT_TIMEOUT)
		return true;
	else
	{
#ifdef USE_POSTMASTER_DEATH_SIGNAL
		postmaster_possibly_dead = true;
#endif
		return false;
	}
#endif							/* WIN32 */
}

/*
 * PostmasterDeathSignalInit - 如果可能的话，在邮政管理员死亡时请求信号
 */
void PostmasterDeathSignalInit(void)
{
#ifdef USE_POSTMASTER_DEATH_SIGNAL
	int			fc_signum = POSTMASTER_DEATH_SIGNAL;

	/* 注册我们的信号处理程序。 */
	pqsignal(fc_signum, fc_postmaster_death_handler);

	/* 请求在父进程退出时发信号。 */
#if defined(PR_SET_PDEATHSIG)
	if (prctl(PR_SET_PDEATHSIG, fc_signum) < 0)
		elog(ERROR, "could not request parent death signal: %m");
#elif defined(PROC_PDEATHSIG_CTL)
	if (procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum) < 0)
		elog(ERROR, "could not request parent death signal: %m");
#else
#error "USE_POSTMASTER_DEATH_SIGNAL set, but there is no mechanism to request the signal"
#endif

	/*
	 * 以防父进程已经消失而我们错过了，最好在第一次调用时
	 * 检查慢速方式。
	 */
	postmaster_possibly_dead = true;
#endif							/* USE_POSTMASTER_DEATH_SIGNAL */
}
